-fPIC
was not set. gcc-12
on my
box was happy, gcc-13
on recent Fedora as used at CRAN was
not. A second error was assuming that saying
SystemRequirements: cmake
would suffice. But hold on
whippersnapper: macOS always has a surprise for you! As
described at the end of the appropriate section in Writing R
Extensions, on that OS you have to go the basement, open
four cupboards, rearrange three shelves and then you get to use it. And
then in doing so (from an added configure
script) I failed
to realize Windows needed a fallback. Gee.
The NEWS entry for this (as well the initial release) follows.
If you like this or other open-source work I do, you can now sponsor me at GitHub.Changes in version 0.0.2 (2023-05-11)
- Explicitly set cmake property for position-independent code
- Help macOS find its cmake binary as detailed also in WRE
- Help Windows with a non-conditional Makevars.win pointing at cmake
- Add more badges to README.md
Changes in version 0.0.1 (2023-05-07)
- Initial release version and CRAN upload
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. Please report excessive re-aggregation in third-party for-profit settings.
netsh interface ip show addressesHere s my output:
PS C:\Users\cjcol> netsh interface ip show addresses "Wi-Fi" Configuration for interface "Wi-Fi" DHCP enabled: Yes IP Address: 172.16.79.53 Subnet Prefix: 172.16.79.0/24 (mask 255.255.255.0) Default Gateway: 172.16.79.1 Gateway Metric: 0 InterfaceMetric: 50Did you follow the instructions linked above in the prerequisites section? If not, take a moment to do so now.
/dev/sdf
. Then:
# Create an msdos partition table $ sudo parted --script /dev/sdf mklabel msdos # Create, format, and label a 10M fat32 partition $ sudo parted --script /dev/sdf mkpart primary fat32 0% 10M $ sudo mkfs.vfat /dev/sdf1 $ sudo fatlabel /dev/sdf1 RPI-FW # Get the UEFI firmware onto the SD card $ sudo mount /dev/sdf1 /mnt/data/ $ sudo unzip Downloads/RPi3_UEFI_Firmware_v1.38.zip -d /mnt/data/ $ sudo umount /mnt/data
ESC
and choose the USB stick from Boot Manager
.
esp
flag on
makes the RPi unbootable. Either configuring the partition as ESP
in debian-installer, or manually with sudo parted --script /dev/sda set 1 esp
on
, breaks boot. In case you accidentally do that, set it back to off
and
the edk2 firmware will boot again.
efibootmgr
or otherwise, but crucially the modifications do not survive
reboot. However, changes made from the firmware interface itself are
persistent. So enter the firmware with ESC
right after booting the RPi,
select Boot Maintenance Manager
Boot Options
Add Boot Option
Your SD card Your ESP partition EFI
debian
shimaa64.efi
.
Choose a creative name for your boot entry (eg: "debian"), save and exit the
firmware interface. Bookworm should be booting fine at this point!
PK-0001.der.pem Subject: O = Debian, CN = Debian UEFI Secure Boot (PK/KEK key), emailAddress = debian-devel@lists.debian.org DB-0001.der.pem Subject: C = US, ST = Washington, L = Redmond, O = Microsoft Corporation, CN = Microsoft Windows Production PCA 2011 DB-0002.der.pem Subject: C = US, ST = Washington, L = Redmond, O = Microsoft Corporation, CN = Microsoft Corporation UEFI CA 2011 KEK-0001.der.pem Subject: O = Debian, CN = Debian UEFI Secure Boot (PK/KEK key), emailAddress = debian-devel@lists.debian.org KEK-0002.der.pem Subject: C = US, ST = Washington, L = Redmond, O = Microsoft Corporation, CN = Microsoft Corporation KEK CA 2011
ESC
.
Select Device Manager
Secure Boot Configuration
Secure Boot Mode
choose Custom Mode
Custom Secure Boot Options
PK Options
Enroll
PK
choose PK-0001.der. Do the same for DB Options
, this time choose
DB-0001.der and DB-0002.der. As you may have guessed by now, the same must
be done for KEK Options
, but adding KEK-0001.der and KEK-0002.der. Save,
exit, reboot. If everything went well, your RPi now has booted with Secure Boot
enabled.
kubectl
may have a completely different impact on the API depending on
usage, for example when listing the whole list of objects (very expensive) vs a single object.
The conclusion was to try avoiding hitting the api-server with LIST calls, and use ResourceVersion which
avoids full-dumps from etcd (which, by the way, is the default when using bare kubectl get
calls). I
already knew some of this, and for example the jobs-framework-emailer was already making use of this
ResourceVersion functionality.
There have been a lot of improvements in the performance side of Kubernetes in recent times, or more
specifically, in how resources are managed and used by the system. I saw a review of resource management from
the perspective of the container runtime and kubelet, and plans to support fancy things like topology-aware
scheduling decisions and dynamic resource claims (changing the pod resource claims without
re-defining/re-starting the pods).
On cluster management, bootstrapping and multi-tenancy
I attended a couple of talks that mentioned kubeadm, and one in particular was from the maintainers
themselves. This was of interest to me because as of today we use it for
Toolforge. They shared all
the latest developments and improvements, and the plans and roadmap for the future, with a special mention to
something they called kubeadm operator , apparently capable of auto-upgrading the cluster, auto-renewing
certificates and such.
I also saw a comparison between the different cluster bootstrappers, which to me confirmed that kubeadm was
the best, from the point of view of being a well established and well-known workflow, plus having a very
active contributor base. The kubeadm developers invited the audience to submit feature requests,
so I did.
The different talks confirmed that the basic unit for multi-tenancy in kubernetes is the namespace. Any
serious multi-tenant usage should leverage this. There were some ongoing conversations, in official sessions
and in the hallway, about the right tool to implement K8s-whitin-K8s, and vcluster
was mentioned enough times for me to be convinced it was the right candidate. This was despite of my impression
that multiclusters / multicloud are regarded as hard topics in the general community. I definitely would like to play
with it sometime down the road.
On networking
I attended a couple of basic sessions that served really well to understand how Kubernetes instrumented the
network to achieve its goal. The conference program had sessions to cover topics ranging from network
debugging recommendations, CNI implementations, to IPv6 support. Also, one of the keynote sessions had a
reference to how kube-proxy is not able to perform NAT for SIP connections, which is interesting because I
believe Netfilter Conntrack could do it if properly configured. One of the conclusions on the CNI front was
that Calico has a massive community adoption (in Netfilter mode), which is reassuring, especially considering
it is the one we use for Toolforge Kubernetes.
On jobs
I attended a couple of talks that were related to HPC/grid-like usages of Kubernetes. I was truly impressed
by some folks out there who were using Kubernetes Jobs on massive scales, such as to train machine learning
models and other fancy AI projects.
It is acknowledged in the community that the early implementation of things like Jobs and CronJobs had some
limitations that are now gone, or at least greatly improved. Some new functionalities have been added as
well. Indexed Jobs, for example, enables each Job to have a number (index) and process a chunk of a larger
batch of data based on that index. It would allow for full grid-like features like sequential (or again,
indexed) processing, coordination between Job and more graceful Job restarts. My first reaction was: Is that
something we would like to enable in Toolforge Jobs Framework?
On policy and security
A surprisingly good amount of sessions covered interesting topics related to policy and security. It was nice
to learn two realities:
-j 4
, built Linux
in ~100 minutes; this was about a year ago, and I m now building linux
6.1, so I timed this again. To get a baseline, I built my kernel from
a just-unpacked tree, just as usual:
# cd /usr/src/linux-source-6.1
# make clean
# make defconfig
# time make -j4
(...)
real 117m30.588s
user 392m41.434s
sys 52m2.556s
make defconfig
without cleaning does not change
much, but I saw it often referenced in firebuild s docs, so I m
leaving it:
# time make -j 4
(...)
real 0m43.822s
user 1m36.577s
sys 0m40.805s
# cd /usr/src/linux-source-6.1
# make clean
# make defconfig
# time firebuild make -j 4
(...)
real 212m58.647s
user 391m49.080s
sys 81m10.758s
# du -sh .cache/firebuild/
4.2G .cache/firebuild/
# cd /usr/src/linux-source-6.1
# make clean
# make defconfig
# time firebuild make -j 4
(...)
real 68m6.621s
user 98m32.514s
sys 31m41.643s
# make defconfig
# time firebuild make -j 4
(...)
real 1m11.872s
user 2m5.807s
sys 1m46.178s
.cache/firebuild
are to blame, as in the case of the media I m
using, those are quite expensive; make
went its way basically
checking date stamps between *.c
and *.o
(yes, very roughly), and
while running under firebuild, I suppose each of these meant an extra
lookup inside the cache.
So Experiment requested, experiment performed!
% mount -t ext4 -l /dev/mapper/foobar-root_1 on / type ext4 (rw,relatime,errors=remount-ro) % sudo pvs PV VG Fmt Attr PSize PFree /dev/mapper/md1_crypt foobar lvm2 a-- 445.95g 430.12g % sudo vgs VG #PV #LV #SN Attr VSize VFree foobar 1 2 0 wz--n- 445.95g 430.12g % sudo lvs LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert root_1 foobar -wi-ao---- <14.90g % lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT [...] sdd 8:48 0 447.1G 0 disk sdd1 8:49 0 571M 0 part /boot/efi sdd2 8:50 0 488M 0 part md0 9:0 0 487M 0 raid1 /boot sdd3 8:51 0 446.1G 0 part md1 9:1 0 446G 0 raid1 md1_crypt 253:0 0 446G 0 crypt foobar-root_1 253:1 0 14.9G 0 lvm / [...] sdf 8:80 0 447.1G 0 disk sdf1 8:81 0 571M 0 part sdf2 8:82 0 488M 0 part md0 9:0 0 487M 0 raid1 /boot sdf3 8:83 0 446.1G 0 part md1 9:1 0 446G 0 raid1 md1_crypt 253:0 0 446G 0 crypt foobar-root_1 253:1 0 14.9G 0 lvm /The actual crypsetup configuration is:
% cat /etc/crypttab md1_crypt UUID=77246138-b666-4151-b01c-5a12db54b28b none luks,discardNow, to automatically open the crypto device during boot we can instead use:
% cat /etc/crypttab md1_crypt UUID=77246138-b666-4151-b01c-5a12db54b28b none luks,discard,keyscript=/etc/initramfs-tools/unlock.sh # touch /etc/initramfs-tools/unlock.sh # chmod 0700 /etc/initramfs-tools/unlock.sh # $EDITOR etc/initramfs-tools/unlock.sh # cat /etc/initramfs-tools/unlock.sh #!/bin/sh echo -n "provide_the_actual_password_here" # update-initramfs -k all -u [...]The server will then boot without prompting for a crypto password. Note that initramfs-tools by default uses an insecure umask of 0022, resulting in the initrd being accessible to everyone. But if you have the dropbear-initramfs package installed, its /usr/share/initramfs-tools/conf-hooks.d/dropbear sets UMASK=0077 , so the resulting /boot/initrd* file should automatically have proper permissions (0600). The cryptsetup hook warns about a permissive umask configuration during update-initramfs runs, but if you want to be sure, explicitly set it via e.g.:
# cat > /etc/initramfs-tools/conf.d/umask << EOF # restrictive umask to avoid non-root access to initrd: UMASK=0077 EOF # update-initramfs -k all -uDisclaimer: Of course you need to trust users with access to /etc/initramfs-tools/unlock.sh as well as the initramfs/initrd on your system. Furthermore you should wipe the boot partition (to destroy the keyfile information) before handing over such a disk. But that is a risk my customer can live with, YMMV.
-j
flag (how many cores to use
simultaneously), starting with single-core, and pushing up until just
a bit over the physical number of cores the CPU has.
Sadly, I lost several of my output images, but the three following are
enough to tell interesting bits of the story:
Of course, I have to add that this is not a scientific comparison; the
server and my laptop have much better I/O than the Raspberry s puny
micro-SD card (and compiling hundreds of thousands of files is quite
an IO-stressed job, even though the full task does exhibit the very
low compared single-threaded performance of the Raspberry even
compared with the Yoga).
No optimizations were done (they would be harmful to the effects I
wanted to show!), the compile was made straight from the upstream
sources.
real 1m32.026s user 4m20.200s sys 2m33.324s real 2m4.111s user 6m31.769s sys 3m53.681s real 0m53.632s user 1m41.334s sys 3m36.227sNext I did a test of building a Linux 6.1.10 kernel with make bzImage -j18 , here are the results from a normal build, first build with firebuild, and second build. The real time is worse with firebuild for this on my machine. I think that the relative speeds of my CPU (reasonably fast 18 core) and storage (two of the slower NVMe devices in a BTRFS RAID-1) is the cause of the first build being relatively so much slower for make bzImage than for building the refpolicy, as the kernel build process involves a lot more data. For the final build I moved ~/.cache/firebuild to a tmpfs (I have 128G of RAM and not much running on my machine at the time of the tests), even then building with firebuild was slightly slower in real time but took significantly less CPU time (user+real being 20mins instead of 36m). I also ran several tests with the kernel source tree on a tmpfs but for unknown reasons those tests each took about 6 minutes. Does firebuild or the Linux kernel build process dislike tmpfs for some reason?
real 2m43.020s user 31m30.551s sys 5m15.279s real 8m49.675s user 64m11.258s sys 19m39.016s real 3m6.858s user 7m47.556s sys 9m22.513s real 2m51.910s user 10m53.870s sys 9m21.307sOne thing I noticed from the kernel build tests is that the total CPU time taken by the firebuild process (as reported by ps) was more than 2/3 of the run time and top usually reported it as taking around 75% of a CPU core. It seems to me that the firebuild process itself is a bottleneck on build speed. Building refpolicy without firebuild has an average of 4.5 cores in use while building the kernel haas 13.5. Unless they make a multi-threaded version of firebuild it seems that it won t give the performance one would hope for from a CPU with 18+ cores. I presume that if I had been running with hyper-threading enabled then firebuild would have been even worse for kernel builds as it would sometimes get on the second thread of a core. It looks like firebuild would perform better on AMD CPUs as they tend to have fewer CPU cores with greater average performance per core so a single CPU core for firebuild will be less limited. I presume that the firebuild developers will make it perform better with large numbers of cores in future, the latest Intel laptop CPUs have 16+ cores and servers with 2*40core CPUs are common. The performance improvement for refpolicy is significant as a portion of build time, but insignificant in terms of real time. A full build of refpolicy doesn t take enough time to get a Coke and reducing it doesn t offer a huge benefit, if Firebuild was available in past years when refpolicy took 20 minutes to build (when DDR2 was the best RAM available) then it would be a different story. There is some potential to optimise the build of refpolicy for the non-firebuild case. Getting it to average more than 4.5 cores in use when there s 18 available should be possible, there are a number of shell for loops in the main Makefile and maybe some of them can be replaced by make constructs to allow running in parallel. If it used 7 cores on average then it would be faster in a regular build than it currently is with firebuild and a hot cache. Any advice from make experts would be appreciated.
SELECT * FROM ( SELECT toStartOfMinute(TimeReceived) AS TimeReceived, DstAddr, SrcPort, dictGetOrDefault('protocols', 'name', Proto, '???') AS Proto, SUM(((((Bytes * SamplingRate) * 8) / 1000) / 1000) / 1000) / 60 AS Gbps, uniq(SrcAddr) AS sources, uniq(SrcCountry) AS countries FROM flows WHERE TimeReceived > now() - INTERVAL 5 MINUTE AND DstNetRole = 'customers' GROUP BY TimeReceived, DstAddr, SrcPort, Proto ) WHERE (Gbps > 1) OR ((Proto = 'UDP') AND (Gbps > 0.2)) OR ((sources > 20) AND (Gbps > 0.1)) OR ((countries > 10) AND (Gbps > 0.1)) ORDER BY TimeReceived DESC, Gbps DESC
TimeReceived | DstAddr | SrcPort | Proto | Gbps | sources | countries |
---|---|---|---|---|---|---|
2023-02-26 17:44:00 | ::ffff:203.0.113.206 |
123 | UDP | 0.102 | 109 | 13 |
2023-02-26 17:43:00 | ::ffff:203.0.113.206 |
123 | UDP | 0.130 | 133 | 17 |
2023-02-26 17:43:00 | ::ffff:203.0.113.68 |
53 | UDP | 0.129 | 364 | 63 |
2023-02-26 17:43:00 | ::ffff:203.0.113.206 |
123 | UDP | 0.113 | 129 | 21 |
2023-02-26 17:42:00 | ::ffff:203.0.113.206 |
123 | UDP | 0.139 | 50 | 14 |
2023-02-26 17:42:00 | ::ffff:203.0.113.206 |
123 | UDP | 0.105 | 42 | 14 |
2023-02-26 17:40:00 | ::ffff:203.0.113.68 |
53 | UDP | 0.121 | 340 | 65 |
65535:666
for this purpose. This is known as a
remote-triggered blackhole (RTBH) and is explained in more detail in RFC 3882.
It is also possible to blackhole the source of the attacks by leveraging
unicast Reverse Path Forwarding (uRPF) from RFC 3704, as explained in RFC 5635. However, uRPF can be a serious tax on your router resources. See
NCS5500 uRPF: Configuration and Impact on Scale for an example of the kind
of restrictions you have to expect when enabling uRPF.
On the advertising side, we can use BIRD. Here is a complete configuration
file to allow any router to collect them:
log stderr all; router id 192.0.2.1; protocol device scan time 10; protocol bgp exporter ipv4 import none; export where proto = "blackhole4"; ; ipv6 import none; export where proto = "blackhole6"; ; local as 64666; neighbor range 192.0.2.0/24 external; multihop; dynamic name "exporter"; dynamic name digits 2; graceful restart yes; graceful restart time 0; long lived graceful restart yes; long lived stale time 3600; # keep routes for 1 hour! protocol static blackhole4 ipv4; route 203.0.113.206/32 blackhole bgp_community.add((65535, 666)); ; route 203.0.113.68/32 blackhole bgp_community.add((65535, 666)); ; protocol static blackhole6 ipv6;
router static vrf public address-family ipv4 unicast 192.0.2.1/32 Null0 description "BGP blackhole" ! address-family ipv6 unicast 2001:db8::1/128 Null0 description "BGP blackhole" ! ! ! route-policy blackhole_ipv4_in_public if destination in (0.0.0.0/0 le 31) then drop endif set next-hop 192.0.2.1 done end-policy ! route-policy blackhole_ipv6_in_public if destination in (::/0 le 127) then drop endif set next-hop 2001:db8::1 done end-policy ! router bgp 12322 neighbor-group BLACKHOLE_IPV4_PUBLIC remote-as 64666 ebgp-multihop 255 update-source Loopback10 address-family ipv4 unicast maximum-prefix 100 90 route-policy blackhole_ipv4_in_public in route-policy drop out long-lived-graceful-restart stale-time send 86400 accept 86400 ! address-family ipv6 unicast maximum-prefix 100 90 route-policy blackhole_ipv6_in_public in route-policy drop out long-lived-graceful-restart stale-time send 86400 accept 86400 ! ! vrf public neighbor 192.0.2.1 use neighbor-group BLACKHOLE_IPV4_PUBLIC description akvorado-1
ForwardingStatus >= 128
as a filter.
While this method is compatible with all routers, it makes the attack successful
as the target is completely unreachable. If your router supports it, Flowspec
can selectively filter flows to stop the attack without impacting the
customer.
flow4 table flowtab4; flow6 table flowtab6; protocol bgp exporter flow4 import none; export where proto = "flowspec4"; ; flow6 import none; export where proto = "flowspec6"; ; # [ ] protocol static flowspec4 flow4; route flow4 dst 203.0.113.68/32; sport = 53; length >= 1476 && <= 1500; proto = 17; bgp_ext_community.add((generic, 0x80060000, 0x00000000)); ; route flow4 dst 203.0.113.206/32; sport = 123; length = 468; proto = 17; bgp_ext_community.add((generic, 0x80060000, 0x00000000)); ; protocol static flowspec6 flow6;
vrf public address-family ipv4 flowspec address-family ipv6 flowspec ! router bgp 12322 address-family vpnv4 flowspec address-family vpnv6 flowspec neighbor-group FLOWSPEC_IPV4_PUBLIC remote-as 64666 ebgp-multihop 255 update-source Loopback10 address-family ipv4 flowspec long-lived-graceful-restart stale-time send 86400 accept 86400 route-policy accept in route-policy drop out maximum-prefix 100 90 validation disable ! address-family ipv6 flowspec long-lived-graceful-restart stale-time send 86400 accept 86400 route-policy accept in route-policy drop out maximum-prefix 100 90 validation disable ! ! vrf public address-family ipv4 flowspec address-family ipv6 flowspec neighbor 192.0.2.1 use neighbor-group FLOWSPEC_IPV4_PUBLIC description akvorado-1
flowspec vrf public address-family ipv4 local-install interface-all ! address-family ipv6 local-install interface-all ! ! !
ForwardingStatus >=
128
.
route flow4 dst 203.0.113.68/32; sport = 53; length >= 1476 && <= 1500; proto = 17; bgp_ext_community.add((generic, 0x80060000, 0x00000000)); ;
quantiles(0.1,
0.9)(Bytes/Packets)
.
The last issue we need to tackle is how to optimize the request: it may need
several seconds to collect the data and it is likely to consume substantial
resources from your ClickHouse database. One solution is to create a
materialized view to pre-aggregate results:
CREATE TABLE ddos_logs ( TimeReceived DateTime, DstAddr IPv6, Proto UInt32, SrcPort UInt16, Gbps SimpleAggregateFunction(sum, Float64), Mpps SimpleAggregateFunction(sum, Float64), sources AggregateFunction(uniqCombined(12), IPv6), countries AggregateFunction(uniqCombined(12), FixedString(2)), size AggregateFunction(quantiles(0.1, 0.9), UInt64) ) ENGINE = SummingMergeTree PARTITION BY toStartOfHour(TimeReceived) ORDER BY (TimeReceived, DstAddr, Proto, SrcPort) TTL toStartOfHour(TimeReceived) + INTERVAL 6 HOUR DELETE ; CREATE MATERIALIZED VIEW ddos_logs_view TO ddos_logs AS SELECT toStartOfMinute(TimeReceived) AS TimeReceived, DstAddr, Proto, SrcPort, sum(((((Bytes * SamplingRate) * 8) / 1000) / 1000) / 1000) / 60 AS Gbps, sum(((Packets * SamplingRate) / 1000) / 1000) / 60 AS Mpps, uniqCombinedState(12)(SrcAddr) AS sources, uniqCombinedState(12)(SrcCountry) AS countries, quantilesState(0.1, 0.9)(toUInt64(Bytes/Packets)) AS size FROM flows WHERE DstNetRole = 'customers' GROUP BY TimeReceived, DstAddr, Proto, SrcPort
ddos_logs
table is using the SummingMergeTree
engine. When the table
receives new data, ClickHouse replaces all the rows with the same sorting key,
as defined by the ORDER BY
directive, with one row which contains summarized
values using either the sum()
function or the explicitly specified aggregate
function (uniqCombined
and quantiles
in our example).3
Finally, we can modify our initial query with the following one:
SELECT * FROM ( SELECT TimeReceived, DstAddr, dictGetOrDefault('protocols', 'name', Proto, '???') AS Proto, SrcPort, sum(Gbps) AS Gbps, sum(Mpps) AS Mpps, uniqCombinedMerge(12)(sources) AS sources, uniqCombinedMerge(12)(countries) AS countries, quantilesMerge(0.1, 0.9)(size) AS size FROM ddos_logs WHERE TimeReceived > now() - INTERVAL 60 MINUTE GROUP BY TimeReceived, DstAddr, Proto, SrcPort ) WHERE (Gbps > 1) OR ((Proto = 'UDP') AND (Gbps > 0.2)) OR ((sources > 20) AND (Gbps > 0.1)) OR ((countries > 10) AND (Gbps > 0.1)) ORDER BY TimeReceived DESC, Gbps DESC
SummingMergeTree
tables,import socket import types from clickhouse_driver import Client as CHClient # Put your SQL query here! SQL_QUERY = " " # How many anti-DDoS rules we want at the same time? MAX_DDOS_RULES = 20 def empty_ruleset(): ruleset = types.SimpleNamespace() ruleset.flowspec = types.SimpleNamespace() ruleset.blackhole = types.SimpleNamespace() ruleset.flowspec.v4 = [] ruleset.flowspec.v6 = [] ruleset.blackhole.v4 = [] ruleset.blackhole.v6 = [] return ruleset current_ruleset = empty_ruleset() client = CHClient(host="clickhouse.akvorado.net") while True: results = client.execute(SQL_QUERY) seen = new_ruleset = empty_ruleset() for (t, addr, proto, port, gbps, mpps, sources, countries, size) in results: if (addr, proto, port) in seen: continue seen[(addr, proto, port)] = True # Flowspec if addr.ipv4_mapped: address = addr.ipv4_mapped rules = new_ruleset.flowspec.v4 table = "flow4" mask = 32 nh = "proto" else: address = addr rules = new_ruleset.flowspec.v6 table = "flow6" mask = 128 nh = "next header" if size[0] == size[1]: length = f"length = int(size[0]) " else: length = f"length >= int(size[0]) && <= int(size[1]) " header = f""" # Time: t # Source: address , protocol: proto , port: port # Gbps/Mpps: gbps:.3 / mpps:.3 , packet size: int(size[0]) <=X<= int(size[1]) # Flows: flows , sources: sources , countries: countries """ rules.append( f""" header route table dst address / mask ; sport = port ; length ; nh = socket.getprotobyname(proto) ; bgp_ext_community.add((generic, 0x80060000, 0x00000000)); ; """ ) # Blackhole if addr.ipv4_mapped: rules = new_ruleset.blackhole.v4 else: rules = new_ruleset.blackhole.v6 rules.append( f""" header route address / mask blackhole bgp_community.add((65535, 666)); ; """ ) new_ruleset.flowspec.v4 = list( set(new_ruleset.flowspec.v4[:MAX_DDOS_RULES]) ) new_ruleset.flowspec.v6 = list( set(new_ruleset.flowspec.v6[:MAX_DDOS_RULES]) ) # TODO: advertise changes by mail, chat, ... current_ruleset = new_ruleset changes = False for rules, path in ( (current_ruleset.flowspec.v4, "v4-flowspec"), (current_ruleset.flowspec.v6, "v6-flowspec"), (current_ruleset.blackhole.v4, "v4-blackhole"), (current_ruleset.blackhole.v6, "v6-blackhole"), ): path = os.path.join("/etc/bird/", f" path .conf") with open(f" path .tmp", "w") as f: for r in rules: f.write(r) changes = ( changes or not os.path.exists(path) or not samefile(path, f" path .tmp") ) os.rename(f" path .tmp", path) if not changes: continue proc = subprocess.Popen( ["birdc", "configure"], stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout, stderr = proc.communicate(None) stdout = stdout.decode("utf-8", "replace") stderr = stderr.decode("utf-8", "replace") if proc.returncode != 0: logger.error( " error:\n \n ".format( "birdc reconfigure", "\n".join( [" O: ".format(line) for line in stdout.rstrip().split("\n")] ), "\n".join( [" E: ".format(line) for line in stderr.rstrip().split("\n")] ), ) )
FORMAT Markdown
to the query.
Publisher: | Tor |
Copyright: | August 2018 |
ISBN: | 1-4668-6573-3 |
Format: | Kindle |
Pages: | 564 |
dpt(1)
wrapper script.
in the last years I got the impression that not all team members are aware
of all the useful tools, & that some more promotion might be called for.
& last week I was in the mood for creating a short demo video to
showcase how I use some dpt(1)
subcommands when updating a
package to a new upstream release. (even though I prefer text over videos
myself :))
probably not a cinematographic masterpiece but as the feedback of a few
viewers has been positive, I'm posting it here as well:
(direct
link as planets ignore iframes )
snapshot.debian.org
snapshot from 20220616T194833Z
, which is a little dated at this point but I understand the rationale behind picking something that works and sticking with it. The kernel is of course a vendor special, based on 5.15.0. Further investigation revealed that the entire X/graphics stack is living in /usr/local
, which isn t overly surprising; it s Imagination based. I was pleasantly surprised to discover there is work to upstream the Imagination support, but I m not planning to run the board with a monitor attached so it s not a high priority for me.
Having discovered all that I decided to see how well a clean Debian unstable install from Debian Ports would go. I had a spare Intel Optane lying around (it s a stupid 22110 M.2 which is too long for any machine I own), so I put it in the slot on the bottom of the board. To my surprise it Just Worked and was detected ok:
# lspci
0000:00:00.0 PCI bridge: PLDA XpressRich-AXI Ref Design (rev 02)
0000:01:00.0 USB controller: VIA Technologies, Inc. VL805/806 xHCI USB 3.0 Controller (rev 01)
0001:00:00.0 PCI bridge: PLDA XpressRich-AXI Ref Design (rev 02)
0001:01:00.0 Non-Volatile memory controller: Intel Corporation NVMe Datacenter SSD [Optane]
# mkfs -t ext4 /dev/nvme0n1p1
# mount /dev/nvme0n1p1 /mnt
# debootstrap --keyring=/etc/apt/trusted.gpg.d/debian-ports-archive-2023.gpg \
unstable /mnt https://deb.debian.org/debian-ports
/boot/extlinux/extlinux.conf
config from /dev/mmcblk1p2
, so I added an additional entry there using the StarFive kernel but pointing to the NVMe device for /
. Made sure to set a root password (not that I ve been bitten by that before, too many times), and rebooted. Success! Well. Sort of. I hit a bunch of problems with having a getty running on ttyS0
as well as one running on hvc0
. The second turns out to be a console device from the RISC-V SBI. I did a systemctl mask serial-getty@hvc0.service
which made things a bit happier, but I was still seeing odd behaviour and output. Turned out I needed to reboot the initramfs as well; the StarFive one was using Plymouth and doing some other stuff that seemed to be confusing matters. An update-initramfs -k 5.15.0-starfive -c
built me a new one and everything was happy.
Next problem; the StarFive kernel doesn t have IPv6 support. StarFive are good citizens and make their 5.15 kernel tree available, so I grabbed it, fed it the existing config, and tweaked some options (including adding IPV6 and SECCOMP, which chrony wanted). Slight hiccup when it turned out trying to do things like make sound modular caused it to fail to compile, and having to backport the fix that allowed the use of GCC 12 (as present in sid), but it got there. So I got cocky and tried to update it to the latest 5.15.94. A few manual merge fixups (which I may or may not have got right, but it compiles and boots for me), and success. Timings:
$ time make -j 4 bindeb-pkg
[linux-image-5.15.94-00787-g1fbe8ac32aa8]
real 37m0.134s
user 117m27.392s
sys 6m49.804s
[155933.434038] nvme nvme0: I/O 436 QID 4 timeout, completion polled
[156173.351166] nvme nvme0: I/O 48 QID 3 timeout, completion polled
[156346.228993] nvme nvme0: I/O 108 QID 3 timeout, completion polled
/etc/kernel/cmdline
will become the new place where the kernel command line can be configured:
. /etc/default/grub echo "root=/dev/mapper/root $GRUB_CMDLINE_LINUX $GRUB_CMDLINE_LINUX_DEFAULT" > /etc/kernel/cmdlineDo not forget to set the correct root file system there, because initramfs-tools does not support discovering it at boot time using the Discoverable Partitions Specification. The installation has been automated since systemd version 252.6-1, so installing the package has the effect of installing sd-boot in the ESP, enabling it in the UEFI boot sequence and then creating boot loader entries for the kernels already installed on the system:
apt install systemd-bootIf needed, it could be manually installed again just by running
bootctl install
.
I like to show the boot menu by default, at least until I will be more familiar with sd-boot:
bootctl set-timeout 4Since other UEFI binaries can be easily chainloaded, I am also going to keep around grub for a while, just to be sure:
cat <<END > /boot/efi/loader/entries/grub.conf title Grub linux /EFI/debian/grubx64.efi ENDAt this point sd-boot works, but I still had to enable secure boot. So far sd-boot has not been signed with a Debian key known to the shim bootloader, so I needed to create a Machine Owner Key (MOK), enroll it in UEFI and then use it to sign everything. I dislike the complexity of mokutil and the other related programs, so after removing it and the boot shim I have decided to use sbctl instead. With it I easily created new keys, enrolled them in the EFI key store and then signed everything:
sbctl create-keys sbctl enroll-keys for file in /boot/efi/*/*/linux /boot/efi/EFI/*/*.efi; do sbctl sign -s $file doneSince there is no sbctl package yet I need to make sure that also the kernels installed in the future will be automatically signed, so I have created a trivial script in
/etc/kernel/install.d/
which automatically runs sbctl sign -s
or sbctl remove-file
.
The Debian wiki SecureBoot page documents how do do this with mokutil and sbsigntool, but I think that sbctl is much friendlier.
Since I am not using the boot shim, I also had to set DisableShimForSecureBoot=true
in /etc/fwupd/uefi_capsule.conf
to make firmware updates work automatically.
As a bonus, I have also added to the boot menu the excellent Debian-based GRML live distribution. Since sd-boot is not capable of loopback-mounting CD-ROM images like grub, I first had to extract the kernel and initramfs and copy them to the ESP:
mount -o loop /boot/grml/grml64-full_2022.11.iso /mnt/ mkdir /boot/efi/grml/ cp /mnt/boot/grml64full/* /boot/efi/grml/ umount /mnt/ cat <<END > /boot/efi/loader/entries/grml.conf title GRML linux /grml/vmlinuz initrd /grml/initrd.img options boot=live bootid=grml64full202211 findiso=/grml/grml64-full_2022.11.iso live-media-path=/live/grml64-full net.ifnames=0 ENDAs expected, after a reboot
bootctl
reports the new security features:
System: Firmware: UEFI 2.70 (Lenovo 0.4496) Firmware Arch: x64 Secure Boot: enabled (user) TPM2 Support: yes Boot into FW: supported Current Boot Loader: Product: systemd-boot 252.5-2 Features: Boot counting Menu timeout control One-shot menu timeout control Default entry control One-shot entry control Support for XBOOTLDR partition Support for passing random seed to OS Load drop-in drivers Support Type #1 sort-key field Support @saved pseudo-entry Support Type #1 devicetree field Boot loader sets ESP information ESP: /dev/disk/by-partuuid/1b767f8e-70fa-5a48-b444-cfe5c272d66e File: /EFI/systemd/systemd-bootx64.efi ...Relevant documentation:
#!/bin/bash
# Either rb3011 (arm) or rb5009 (arm64)
#HOSTNAME="rb3011"
HOSTNAME="rb5009"
if [ "x$ HOSTNAME " == "xrb3011" ]; then
ARCH=armhf
elif [ "x$ HOSTNAME " == "xrb5009" ]; then
ARCH=arm64
else
echo "Unknown host: $ HOSTNAME "
exit 1
fi
BASE_DIR=$(dirname $0)
IMAGE_FILE=$(mktemp --tmpdir router.$ ARCH .XXXXXXXXXX.img)
MOUNT_POINT=$(mktemp -p /mnt -d router.$ ARCH .XXXXXXXXXX)
# Build and mount an ext4 image file to put the root file system in
dd if=/dev/zero bs=1 count=0 seek=1G of=$ IMAGE_FILE
mkfs -t ext4 $ IMAGE_FILE
mount -o loop $ IMAGE_FILE $ MOUNT_POINT
# Add dpkg excludes
mkdir -p $ MOUNT_POINT /etc/dpkg/dpkg.cfg.d/
cat <<EOF > $ MOUNT_POINT /etc/dpkg/dpkg.cfg.d/path-excludes
# Exclude docs
path-exclude=/usr/share/doc/*
# Only locale we want is English
path-exclude=/usr/share/locale/*
path-include=/usr/share/locale/en*/*
path-include=/usr/share/locale/locale.alias
# No man pages
path-exclude=/usr/share/man/*
EOF
# Setup fstab + mtab
echo "# Empty fstab as root is pre-mounted" > $ MOUNT_POINT /etc/fstab
ln -s ../proc/self/mounts $ MOUNT_POINT /etc/mtab
# Setup hostname
echo $ HOSTNAME > $ MOUNT_POINT /etc/hostname
# Add the root SSH keys
mkdir -p $ MOUNT_POINT /root/.ssh/
cat <<EOF > $ MOUNT_POINT /root/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAv8NkUeVdsVdegS+JT9qwFwiHEgcC9sBwnv6RjpH6I4d3im4LOaPOatzneMTZlH8Gird+H4nzluciBr63hxmcFjZVW7dl6mxlNX2t/wKvV0loxtEmHMoI7VMCnrWD0PyvwJ8qqNu9cANoYriZRhRCsBi27qPNvI741zEpXN8QQs7D3sfe4GSft9yQplfJkSldN+2qJHvd0AHKxRdD+XTxv1Ot26+ZoF3MJ9MqtK+FS+fD9/ESLxMlOpHD7ltvCRol3u7YoaUo2HJ+u31l0uwPZTqkPNS9fkmeCYEE0oXlwvUTLIbMnLbc7NKiLgniG8XaT0RYHtOnoc2l2UnTvH5qsQ== noodles@earth.li
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDQb9+qFemcwKhey3+eTh5lxp+3sgZXW2HQQEZMt9hPvVXk+MiiNMx9WUzxPJnwXqlmmVdKsq+AvjA0i505Pp8fIj5DdUBpSqpLghmzpnGuob7SSwXYj+352hjD52UC4S0KMKbIaUpklADgsCbtzhYYc4WoO8F7kK63tS5qa1XSZwwRwPbYOWBcNocfr9oXCVWD9ismO8Y0l75G6EyW8UmwYAohDaV83pvJxQerYyYXBGZGY8FNjqVoOGMRBTUcLj/QTo0CDQvMtsEoWeCd0xKLZ3gjiH3UrknkaPra557/TWymQ8Oh15aPFTr5FvKgAlmZaaM0tP71SOGmx7GpCsP4jZD1Xj/7QMTAkLXb+Ou6yUOVM9J4qebdnmF2RGbf1bwo7xSIX6gAYaYgdnppuxqZX1wyAy+A2Hie4tUjMHKJ6OoFwBsV1sl+3FobrPn6IuulRCzsq2aLqLey+PHxuNAYdSKo7nIDB3qCCPwHlDK52WooSuuMidX4ujTUw7LDTia9FxAawudblxbrvfTbg3DsiDBAOAIdBV37HOAKu3VmvYSPyqT80DEy8KFmUpCEau59DID9VERkG6PWPVMiQnqgW2Agn1miOBZeIQV8PFjenAySxjzrNfb4VY/i/kK9nIhXn92CAu4nl6D+VUlw+IpQ8PZlWlvVxAtLonpjxr9OTw== noodles@yubikey
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0I8UHj4IpfqUcGE4cTvLB0d2xmATSUzqtxW6ZhGbZxvQDKJesVW6HunrJ4NFTQuQJYgOXY/o82qBpkEKqaJMEFHTCjcaj3M6DIaxpiRfQfs0nhtzDB6zPiZn9Suxb0s5Qr4sTWd6iI9da72z3hp9QHNAu4vpa4MSNE+al3UfUisUf4l8TaBYKwQcduCE0z2n2FTi3QzmlkOgH4MgyqBBEaqx1tq7Zcln0P0TYZXFtrxVyoqBBIoIEqYxmFIQP887W50wQka95dBGqjtV+d8IbrQ4pB55qTxMd91L+F8n8A6nhQe7DckjS0Xdla52b9RXNXoobhtvx9K2prisagsHT noodles@cup
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK6iGog3WbNhrmrkglNjVO8/B6m7mN6q1tMm1sXjLxQa+F86ETTLiXNeFQVKCHYrk8f7hK0d2uxwgj6Ixy9k0Cw= noodles@sevai
EOF
# Bootstrap our install
debootstrap \
--arch=$ ARCH \
--include=collectd-core,conntrack,dnsmasq,ethtool,iperf3,kexec-tools,mosquitto,mtd-utils,mtr-tiny,ppp,tcpdump,rng-tools5,ssh,watchdog,wget \
--exclude=dmidecode,isc-dhcp-client,isc-dhcp-common,makedev,nano \
bullseye $ MOUNT_POINT https://deb.debian.org/debian/
debootstrap
step, including a bunch of extra packages that we want.
# Install mqtt-arp
cp $ BASE_DIR /debs/mqtt-arp_1_$ ARCH .deb $ MOUNT_POINT /tmp
chroot $ MOUNT_POINT dpkg -i /tmp/mqtt-arp_1_$ ARCH .deb
rm $ MOUNT_POINT /tmp/mqtt-arp_1_$ ARCH .deb
# Frob the mqtt-arp config so it starts after mosquitto
sed -i -e 's/After=.*/After=mosquitto.service/' $ MOUNT_POINT /lib/systemd/system/mqtt-arp.service
# Frob watchdog so it starts earlier than multi-user
sed -i -e 's/After=.*/After=basic.target/' $ MOUNT_POINT /lib/systemd/system/watchdog.service
# Make sure the watchdog is poking the device file
sed -i -e 's/^#watchdog-device/watchdog-device/' $ MOUNT_POINT /etc/watchdog.conf
# Clean up docs + locales
rm -r $ MOUNT_POINT /usr/share/doc/*
rm -r $ MOUNT_POINT /usr/share/man/*
for dir in $ MOUNT_POINT /usr/share/locale/*/; do
if [ "$ dir " != "$ MOUNT_POINT /usr/share/locale/en/" ]; then
rm -r $ dir
fi
done
# Set root password to root
echo "root:root" chroot $ MOUNT_POINT chpasswd
# Add security to sources.list + update
echo "deb https://security.debian.org/debian-security bullseye-security main" >> $ MOUNT_POINT /etc/apt/sources.list
chroot $ MOUNT_POINT apt update
chroot $ MOUNT_POINT apt -y full-upgrade
chroot $ MOUNT_POINT apt clean
# Cleanup the APT lists
rm $ MOUNT_POINT /var/lib/apt/lists/www.*
rm $ MOUNT_POINT /var/lib/apt/lists/security.*
# Disable the daily APT timer
rm $ MOUNT_POINT /etc/systemd/system/timers.target.wants/apt-daily.timer
# Disable daily dpkg backup
cat <<EOF > $ MOUNT_POINT /etc/cron.daily/dpkg
#!/bin/sh
# Don't do the daily dpkg backup
exit 0
EOF
# We don't want a persistent systemd journal
rmdir $ MOUNT_POINT /var/log/journal
# Enable nftables
ln -s /lib/systemd/system/nftables.service \
$ MOUNT_POINT /etc/systemd/system/sysinit.target.wants/nftables.service
# Add systemd-coredump + systemd-timesync user / group
echo "systemd-timesync:x:998:" >> $ MOUNT_POINT /etc/group
echo "systemd-coredump:x:999:" >> $ MOUNT_POINT /etc/group
echo "systemd-timesync:!*::" >> $ MOUNT_POINT /etc/gshadow
echo "systemd-coredump:!*::" >> $ MOUNT_POINT /etc/gshadow
echo "systemd-timesync:x:998:998:systemd Time Synchronization:/:/usr/sbin/nologin" >> $ MOUNT_POINT /etc/passwd
echo "systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin" >> $ MOUNT_POINT /etc/passwd
echo "systemd-timesync:!*:47358::::::" >> $ MOUNT_POINT /etc/shadow
echo "systemd-coredump:!*:47358::::::" >> $ MOUNT_POINT /etc/shadow
# Create /etc/.pwd.lock, otherwise it'll end up in the overlay
touch $ MOUNT_POINT /etc/.pwd.lock
chmod 600 $ MOUNT_POINT /etc/.pwd.lock
# Copy config files
cp --recursive --preserve=mode,timestamps $ BASE_DIR /etc/* $ MOUNT_POINT /etc/
cp --recursive --preserve=mode,timestamps $ BASE_DIR /etc-$ ARCH /* $ MOUNT_POINT /etc/
chroot $ MOUNT_POINT chown mosquitto /etc/mosquitto/mosquitto.users
chroot $ MOUNT_POINT chown mosquitto /etc/ssl/mqtt.home.key
# Build symlinks into flash for boot / modules
ln -s /mnt/flash/lib/modules $ MOUNT_POINT /lib/modules
rmdir $ MOUNT_POINT /boot
ln -s /mnt/flash/boot $ MOUNT_POINT /boot
# Put our git revision into os-release
echo -n "GIT_VERSION=" >> $ MOUNT_POINT /etc/os-release
(cd $ BASE_DIR ; git describe --tags) >> $ MOUNT_POINT /etc/os-release
# Add some stuff to root's .bashrc
cat << EOF >> $ MOUNT_POINT /root/.bashrc
alias ls='ls -F --color=auto'
eval "\$(dircolors)"
case "\$TERM" in
xterm* rxvt*)
PS1="\\[\\e]0;\\u@\\h: \\w\a\\]\$PS1"
;;
*)
;;
esac
EOF
# Build the squashfs
mksquashfs $ MOUNT_POINT /tmp/router.$ ARCH .squashfs \
-comp xz
# Save the installed package list off
chroot $ MOUNT_POINT dpkg --get-selections > /tmp/wip-installed-packages
/etc
, shared across both routers are the following:
apt/apt.conf.d/10periodic
, apt/apt.conf.d/local-recommends
default/locale
dnsmasq.conf
, dnsmasq.d/dhcp-ranges
, dnsmasq.d/static-ips
hosts
, resolv.conf
sysctl.conf
logrotate.conf
, rsyslog.conf
mosquitto/mosquitto.users
, mosquitto/conf.d/ssl.conf
, mosquitto/conf.d/users.conf
, mosquitto/mosquitto.acl
, mosquitto/mosquitto.conf
mqtt-arp.conf
ssl/lets-encrypt-r3.crt
, ssl/mqtt.home.key
, ssl/mqtt.home.crt
ppp/ip-up.d/0000usepeerdns
, ppp/ipv6-up.d/defaultroute
, ppp/pap-secrets
, ppp/chap-secrets
network/interfaces.d/pppoe-wan
nftables.conf
dnsmasq.d/interfaces
network/interfaces.d/eth0
, network/interfaces.d/p1
, network/interfaces.d/p2
, network/interfaces.d/p7
, network/interfaces.d/p8
ppp/peers/aquiss
ssh/ssh_host_ecdsa_key
, ssh/ssh_host_ed25519_key
, ssh/ssh_host_rsa_key
, ssh/ssh_host_ecdsa_key.pub
, ssh/ssh_host_ed25519_key.pub
, ssh/ssh_host_rsa_key.pub
collectd/collectd.conf
, collectd/collectd.conf.d/network.conf
.proto
). The protoc
compiler and a language-specific plugin convert it into
code:
$ head flow-4.proto syntax = "proto3"; package decoder; option go_package = "akvorado/inlet/flow/decoder"; message FlowMessagev4 uint64 TimeReceived = 2; uint32 SequenceNum = 3; uint64 SamplingRate = 4; uint32 FlowDirection = 5; $ protoc -I=. --plugin=protoc-gen-go --go_out=module=akvorado:. flow-4.proto $ head inlet/flow/decoder/flow-4.pb.go // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.0 // protoc v3.21.12 // source: inlet/flow/data/schemas/flow-4.proto package decoder import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect"
goos: linux goarch: amd64 pkg: akvorado/inlet/flow cpu: AMD Ryzen 5 5600X 6-Core Processor initial.txt final.txt sec/op sec/op vs base Netflow/with_encoding-12 12.963 2% 7.836 1% -39.55% (p=0.000 n=10) Sflow/with_encoding-12 19.37 1% 10.15 2% -47.63% (p=0.000 n=10)
Decode()
method is a thin layer above
GoFlow2 producer and stores the decoded data into the in-memory structure
generated by protoc
. Later, some of the data will be encoded directly during
flow decoding. This is why we measure both the decoding and the
encoding.2
func BenchmarkDecodeEncodeSflow(b *testing.B) r := reporter.NewMock(b) sdecoder := sflow.New(r) data := helpers.ReadPcapPayload(b, filepath.Join("decoder", "sflow", "testdata", "data-1140.pcap")) for _, withEncoding := range []bool true, false title := map[bool]string true: "with encoding", false: "without encoding", [withEncoding] var got []*decoder.FlowMessage b.Run(title, func(b *testing.B) for i := 0; i < b.N; i++ got = sdecoder.Decode(decoder.RawFlow Payload: data, Source: net.ParseIP("127.0.0.1"), ) if withEncoding for _, flow := range got buf := []byte buf = protowire.AppendVarint(buf, uint64(proto.Size(flow))) proto.MarshalOptions .MarshalAppend(buf, flow) )
google.golang.org/protobuf
is not the most
efficient one. For a long time, people were relying on gogoprotobuf.
However, the project is now deprecated. A good replacement is
vtprotobuf.3
goos: linux goarch: amd64 pkg: akvorado/inlet/flow cpu: AMD Ryzen 5 5600X 6-Core Processor initial.txt bench-2.txt sec/op sec/op vs base Netflow/with_encoding-12 12.96 2% 10.28 2% -20.67% (p=0.000 n=10) Netflow/without_encoding-12 8.935 2% 8.975 2% ~ (p=0.143 n=10) Sflow/with_encoding-12 19.37 1% 16.67 2% -13.93% (p=0.000 n=10) Sflow/without_encoding-12 14.62 3% 14.87 1% +1.66% (p=0.007 n=10)
.proto
file. The wire format is simple and rely a lot on variable-width
integers.
Variable-width integers, or varints, are an efficient way of encoding unsigned
integers using a variable number of bytes, from one to ten, with small values
using fewer bytes. They work by splitting integers into 7-bit payloads and using
the 8th bit as a continuation indicator, set to 1 for all payloads
except the last.
For our usage, we only need two types: variable-width
integers and byte sequences. A byte sequence is encoded by prefixing it by its
length as a varint. When a message is encoded, each key-value pair is turned
into a record consisting of a field number, a wire type, and a payload. The
field number and the wire type are encoded as a single variable-width integer
called a tag.
We use the following low-level functions to build the output buffer:
protowire.AppendTag()
encodes a tag,protowire.AppendVarint()
encodes a variable-width integer, andprotowire.AppendBytes()
append bytes as is.ProtobufIndex
) and to generate a proto definition file (fields starting with
Protobuf
):
type Column struct Key ColumnKey Name string Disabled bool // [ ] // For protobuf. ProtobufIndex protowire.Number ProtobufType protoreflect.Kind // Uint64Kind, Uint32Kind, ProtobufEnum map[int]string ProtobufEnumName string ProtobufRepeated bool
protowire
functions to directly
encode the fields while decoding the flows. They skip disabled fields or
non-repeated fields already encoded. Here is an excerpt of the sFlow
decoder:
sch.ProtobufAppendVarint(bf, schema.ColumnBytes, uint64(recordData.Base.Length)) sch.ProtobufAppendVarint(bf, schema.ColumnProto, uint64(recordData.Base.Protocol)) sch.ProtobufAppendVarint(bf, schema.ColumnSrcPort, uint64(recordData.Base.SrcPort)) sch.ProtobufAppendVarint(bf, schema.ColumnDstPort, uint64(recordData.Base.DstPort)) sch.ProtobufAppendVarint(bf, schema.ColumnEType, helpers.ETypeIPv4)
type FlowMessage struct TimeReceived uint64 SamplingRate uint32 // For exporter classifier ExporterAddress netip.Addr // For interface classifier InIf uint32 OutIf uint32 // For geolocation or BMP SrcAddr netip.Addr DstAddr netip.Addr NextHop netip.Addr // Core component may override them SrcAS uint32 DstAS uint32 GotASPath bool // protobuf is the protobuf representation for the information not contained above. protobuf []byte protobufSet bitset.BitSet
protobuf
slice holds encoded data. It is initialized with a capacity of
500 bytes to avoid resizing during encoding. There is also some reserved room at
the beginning to be able to encode the total size as a variable-width integer.
Upon finalizing encoding, the remaining fields are added and the message length
is prefixed:
func (schema *Schema) ProtobufMarshal(bf *FlowMessage) []byte schema.ProtobufAppendVarint(bf, ColumnTimeReceived, bf.TimeReceived) schema.ProtobufAppendVarint(bf, ColumnSamplingRate, uint64(bf.SamplingRate)) schema.ProtobufAppendIP(bf, ColumnExporterAddress, bf.ExporterAddress) schema.ProtobufAppendVarint(bf, ColumnSrcAS, uint64(bf.SrcAS)) schema.ProtobufAppendVarint(bf, ColumnDstAS, uint64(bf.DstAS)) schema.ProtobufAppendIP(bf, ColumnSrcAddr, bf.SrcAddr) schema.ProtobufAppendIP(bf, ColumnDstAddr, bf.DstAddr) // Add length and move it as a prefix end := len(bf.protobuf) payloadLen := end - maxSizeVarint bf.protobuf = protowire.AppendVarint(bf.protobuf, uint64(payloadLen)) sizeLen := len(bf.protobuf) - end result := bf.protobuf[maxSizeVarint-sizeLen : end] copy(result, bf.protobuf[end:end+sizeLen]) return result
-benchmem
flag to monitor allocation
numbers. Each allocation incurs an additional cost to the garbage collector. The
Go profiler is a valuable tool for identifying areas of code that can be
optimized:
$ go test -run=__nothing__ -bench=Netflow/with_encoding \ > -benchmem -cpuprofile profile.out \ > akvorado/inlet/flow goos: linux goarch: amd64 pkg: akvorado/inlet/flow cpu: AMD Ryzen 5 5600X 6-Core Processor Netflow/with_encoding-12 143953 7955 ns/op 8256 B/op 134 allocs/op PASS ok akvorado/inlet/flow 1.418s $ go tool pprof profile.out File: flow.test Type: cpu Time: Feb 4, 2023 at 8:12pm (CET) Duration: 1.41s, Total samples = 2.08s (147.96%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) web
goos: linux goarch: amd64 pkg: akvorado/inlet/flow cpu: AMD Ryzen 5 5600X 6-Core Processor bench-2.txt bench-3.txt sec/op sec/op vs base Netflow/with_encoding-12 10.284 2% 7.758 3% -24.56% (p=0.000 n=10) Netflow/without_encoding-12 8.975 2% 7.304 2% -18.61% (p=0.000 n=10) Sflow/with_encoding-12 16.67 2% 14.26 1% -14.50% (p=0.000 n=10) Sflow/without_encoding-12 14.87 1% 13.56 2% -8.80% (p=0.000 n=10)
github.com/jhump/protoreflect
: the
protoparse
package parses the proto definition file we generate and the
dynamic
package decodes the messages. Check the ProtobufDecode()
method for more details.4
To get the final figures, I have also optimized the decoding in GoFlow2. It
was relying heavily on binary.Read()
. This function may use
reflection in certain cases and each call allocates a byte array to read data.
Replacing it with a more efficient version provides the following
improvement:
goos: linux goarch: amd64 pkg: akvorado/inlet/flow cpu: AMD Ryzen 5 5600X 6-Core Processor bench-3.txt bench-4.txt sec/op sec/op vs base Netflow/with_encoding-12 7.758 3% 7.365 2% -5.07% (p=0.000 n=10) Netflow/without_encoding-12 7.304 2% 6.931 3% -5.11% (p=0.000 n=10) Sflow/with_encoding-12 14.256 1% 9.834 2% -31.02% (p=0.000 n=10) Sflow/without_encoding-12 13.559 2% 9.353 2% -31.02% (p=0.000 n=10)
Notice Some paragraphs were editorialized by ChatGPT, using editorialize and keep it short as a prompt. The result was proofread by a human for correctness. The main idea is that ChatGPT should be better at English than me.
vtprotobuf
generates more optimized Go code by removing an
abstraction layer. It directly generates the code encoding each field to
bytes:
if m.OutIfSpeed != 0 i = encodeVarint(dAtA, i, uint64(m.OutIfSpeed)) i-- dAtA[i] = 0x6 i-- dAtA[i] = 0xd8
protoprint
package to generate proto definition
file. I did not use it.
Welcome to the first report for 2023 from the Reproducible Builds project! In these reports we try and outline the most important things that we have been up to over the past month, as well as the most important things in/around the community. As a quick recap, the motivation behind the reproducible builds effort is to ensure no malicious flaws can be deliberately introduced during compilation and distribution of the software that we run on our devices. As ever, if you are interested in contributing to the project, please visit our Contribute page on our website.
the default compression for Git archives has recently changed. As result, archives downloaded from GitHub may have different checksums even though the contents are completely unchanged.This change (which was brought up on our mailing list last October) would have had quite wide-ranging implications for anyone wishing to validate and verify downloaded archives using cryptographic signatures. However, GitHub reversed this decision, updating their original announcement with a message that We are reverting this change for now. More details to follow. It appears that this was informed in part by an in-depth discussion in the GitHub Community issue tracker.
In this report, the software supply chains of the most popular Ethereum clients are cataloged and analyzed. The dependency graphs of Ethereum clients developed in Go, Rust, and Java, are studied. These client are Geth, Prysm, OpenEthereum, Lighthouse, Besu, and Teku. To do so, their dependency graphs are transformed into a unified format. Quantitative metrics are used to depict the software supply chain of the blockchain. The results show a clear difference in the size of the software supply chain required for the execution layer and consensus layer of Ethereum.
Dockerfile
and that my talk will also mention how to pin the apt/dnf/apk/pacman packages with my repro-get
tool.
SOURCE_DATE_EPOCH
environment variable this month. This means that release tarballs of the Signal desktop client do not embed nondeterministic release information. [ ][ ]
In response to [ ] criticisms, we started encouraging new apps to enable reproducible builds. It turns out that reproducible builds are not so difficult to achieve for many apps. In the past few months we ve gotten many more reproducible apps in F-Droid than before. Currently we can t highlight which apps are reproducible in the client, so maybe you haven t noticed that there are many new apps signed with upstream developers keys.(There was a discussion about this post on Hacker News.) In addition:
baseline.profm
files are nondeterministic, developed a workaround, and provided all the details required for a fix. As they note, this issue has now been fixed but the fix is not yet part of an official Android Gradle plugin release.
.dex
files. [ ]
0.2.0
and 0.2.1
releases of reproducible-apk-tools, a suite of tools to help make .apk
files reproducible. Several new subcommands and scripts were added, and a number of bugs were fixed as well [ ][ ]. They also updated the F-Droid website to improve the reproducibility-related documentation. [ ][ ]
31.0.0
and 32.0.0
(unlike earlier and later versions) have a zipalign
command that produces incorrect padding.
.zip
entries in .apk
files [ ] and then newline differences between building on Windows versus Linux that can make builds not reproducible as well. [ ] (Note that these links may require a Google account to view.)
mc
(#828683)gtk-sharp3
(#989965 & #989966)tracker.debian.org
service display results of reproducible rebuilds, not just reproducible CI results.
Elsewhere in Debian, strip-nondeterminism is our tool to remove specific non-deterministic results from a completed build. This month, version 1.13.1-1
was uploaded to Debian unstable by Holger Levsen, including a fix by FC Stegerman (obfusk) to update a regular expression for the latest version of file(1)
[ ]. (#1028892)
Lastly, 65 reviews of Debian packages were added, 21 were updated and 35 were removed this month adding to our knowledge about identified issues.
SOURCE_DATE_EPOCH
. [ ]
231
, 232
, 233
and 234
to Debian:
from __future__ import print_function
import anymore. [ ]extras_require.json
handling. [ ]Recommends
into a separate Python script. [ ]debian/tests/control
after merging support for PyPDF support. [ ]cd-iccdump
binary. [ ]test_text_proper_indentation
test to support the latest version(s) of file(1)
. [ ]extras_require.json
file to store some build/release metadata, instead of accessing the internet. [ ]file(1)
regular expression. [ ]argparse
(date-related issue)asciimatics
(build failure)asyncpg
(fails to build in 2032)cpython
(fails to build in 2038)django
(fails to build in 2038)grandorgue
(.zip
-related issue)libarchive
(fails to build in 2038)libarchive
(fails to build in 2038)librcc
(date)mbedtls
(fails to build in 2023)mozilla-nss
(fails to build in 2023)ocaml-rpm-macros
(fix fallout from an RPM-related change)perl HTTP::Cookies
(fails to build in 2038)python-aiosmtplib/python-trustme
(fails to build in 2038 due to SSL certificate)python-bmap
(fails to build in 2024)python-compileall2
(fails to build in 2038)taskwarrior
(python-tasklib
fails to build in 2038)taskwarrior
(fix fails to build in 2038)wrk
(hash ordering issue)xemacs
(fails to build in 2038 stuck)click
.towncrier
.unifrac-tools
.hamster-time-tracker
.accel-config
.python-miio
.python-graphviz
.ectrans
.fiat-ecmwf
.node-katex
.aribas
.tbox
.borgbackup2
.dnf-plugins-core
.refpolicy
.file(1)
(which is used by reproducible builds tools like diffoscope and strip-nondeterminism) that improve detection of various file formats are now included in the Debian packaging. [ ].json
or .txt
) for LTS suites and older in order to save diskspace on the Jenkins host. [ ]pbuilder
base less frequently for the stretch
, bookworm
and experimental
suites. [ ]chroot-run
script to correctly manage /dev
and /dev/pts
. [ ][ ][ ]V=s
flag to enable debugging. [ ]
#reproducible-builds
on irc.oftc.net
.
rb-general@lists.reproducible-builds.org
BEGIN:VEVENT
DTSTART;TZID=Australia/Sydney:20230206T000000
DTEND;TZID=Australia/Sydney:20230206T000000
SUMMARY:School Term starts
END:VEVENT
The event starting and stopping date and time are the DTSTART and DTEND lines. Both of them have the date of 2023/02/06 or 6th February 2023 and a time of 00:00:00 or midnight. So the calendar is doing the right thing, we need to fix the feed!
The Fix
I wrote a quick and dirty PHP script to download the feed from the real site, change the DTSTART and DTEND lines to all-day events and leave the rest of it alone.
<?php $site = $_GET['s']; if ($site == 'site1') $REMOTE_URL='https://site1.example.net/ical_feed'; elseif ($site == 'site2') $REMOTE_URL='https://site2.example.net/ical_feed'; else http_response_code(400); die(); $fp = fopen($REMOTE_URL, "r"); if (!$fp) die("fopen"); header('Content-Type: text/calendar'); while (( $line = fgets($fp, 1024)) !== false) $line = preg_replace( '/^(DTSTART DTEND);[^:]+:([0-9] 8 )T000[01]00/', '$ 1 ;VALUE=DATE:$ 2 ', $line); echo $line; ?>It s pretty quick and nasty but gets the job done. So what is it doing?
s
and match it to either site1 or site2 to obtain the URL. If you only had one site to fix you could just set the REMOTE_URL
variable.fopen()
and nasty error handling.while
loop to read the contents of the remote site line by line.preg_replace
is a Perl regular expression replacement. The PCRE is:
BEGIN:VEVENT
DTSTART;VALUE=DATE:20230206
DTEND;VALUE=DATE:20230206
SUMMARY:School Term starts
END:VEVENT
The calendar then shows it properly as an all-day event. I would check the script works before doing the next step. You can use things like curl or wget to download it. If you use a normal browser, it will probably just download the translated file.
If you re not seeing the right thing then it s probably the PCRE failing. You can check it online with a regex checker such as https://regex101.com. The site has saved my PCRE and match so you got something to start with.
Calendar settings
The last thing to do is to change the URL in your calendar settings. Each calendar system has a different way of doing it. For Google Calendar they provide instructions and you want to follow the section titled Use a link to add a public Calendar .
The URL here is not the actual site s URL (which you would have put into the REMOTE_URL variable before) but the URL of your script plus the ?s=site1 part. So if you put your script aliased to /myical.php and the site ID was site1 and your website is www.example.com the URL would be https://www.example.com/myical.php?s=site1 .
You should then see the events appear as all-day events on your calendar.
Next.